﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Sofire.DataComm.Remoting.Client
{
    /// <summary>
    /// 表示远程交互的注册信息。
    /// </summary>
    public partial class RegisterInfo
    {
        private string _Name;
        /// <summary>
        /// 获取一个值，表示注册信息的标识名称。
        /// </summary>
        public string Name
        {
            get
            {
                return this._Name;
            }
        }

        private string _Address;
        /// <summary>
        /// 获取一个值，表示注册信息的地址。
        /// </summary>
        public string Address
        {
            get
            {
                return this._Address;
            }
        }

        private string _Username;
        /// <summary>
        /// 设置或获取一个值，表示注册信息的账户。
        /// </summary>
        public string Username
        {
            get
            {
                if(_Username == null) return string.Empty;
                return this._Username;
            }
            set
            {
                this._Username = value;
            }
        }

        private string _Password;
        /// <summary>
        /// 设置或获取一个值，表示注册信息的密码。
        /// </summary>
        public string Password
        {
            get
            {
                if(_Password == null) return string.Empty;
                return this._Password;
            }
            set
            {
                this._Password = value;
            }
        }

        private Type _ChannelType;
        /// <summary>
        /// 获取一个值，表示注册信息的契约类型。
        /// </summary>
        public Type ChannelType
        {
            get
            {
                return this._ChannelType;
            }
        }

        private Type DynamicType;
        private List<MethodImpl> _ContractMethods;
        /// <summary>
        /// 获取契约的方法集合。
        /// </summary>
        public IEnumerable<MethodImpl> ContractMethods
        {
            get
            {
                return this._ContractMethods;
            }
        }

        internal int FindMethodIdent(MethodInfo methodInfo)
        {
            for(int i = 0 ; i < this._ContractMethods.Count ; i++)
            {
                if(this._ContractMethods[i].MethodInfo == methodInfo) return i;
            }
            return -1;
        }

        /// <summary>
        /// 初始化 <see cref="Sofire.DataComm.Remoting.Client.RegisterInfo"/> 的新实例。
        /// </summary>
        /// <param name="name">注册信息的标识名称。</param>
        /// <param name="channelType">服务契约的类型。</param>
        /// <param name="address">服务的地址。</param>
        public RegisterInfo(string name, Type channelType, string address) : this(name, channelType, address, null, null) { }

        /// <summary>
        /// 初始化 <see cref="Sofire.DataComm.Remoting.Client.RegisterInfo"/> 的新实例。
        /// </summary>
        /// <param name="name">注册信息的标识名称。</param>
        /// <param name="channelType">服务契约的类型。</param>
        /// <param name="address">服务的地址。</param>
        /// <param name="username">契约用户。可以为 null 值。</param>
        /// <param name="password">契约密码。可以为 null 值。</param>
        public RegisterInfo(string name, Type channelType, string address, string username, string password)
        {
            this._Name = name;
            this._Address = address;
            this._Username = username;
            this._Password = password;
            this._ChannelType = channelType;
        }
    }

    public partial class RegisterInfo
    {

        private const TypeAttributes ChannelTypeAttributes = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed;
        private const MethodAttributes ChanneMethodAttributes = MethodAttributes.Public | MethodAttributes.Virtual;

        private static readonly Type[] ChannelConstructorArgTypes = new Type[] { typeof(RegisterInfo) };
        //private static readonly Type[] ProxyConstructorArgTypes = new Type[] { typeof(string) };//address
        private static readonly Type ChannelBaseType = typeof(RemotingClient);

        private static readonly MethodInfo ChannelInvokeMethod = ChannelBaseType.GetMethod("InvokeMethod", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { Types.Int32, Types.ObjectArray }, null);

        private static readonly Dictionary<Type, OpCode> LdindOpCodeTypeMap;
        private static readonly Dictionary<Type, OpCode> StindOpCodeTypeMap;
        private readonly static Object LocalMethodsSyncObject = new object();

        private class TypeInfo
        {
            public Type ServiceType;
            public List<MethodImpl> ContractMethods;
        }

        private static readonly Dictionary<Type, TypeInfo> TypeCahers = new Dictionary<Type, TypeInfo>();

        //internal readonly static List<MethodImpl> LocalMethods = new List<MethodImpl>(11);

        static RegisterInfo()
        {
            // 加载指定索引（在这之前已经 POP）的值作为 XXX 类型读取出来
            LdindOpCodeTypeMap = new Dictionary<Type, OpCode>(12);
            LdindOpCodeTypeMap.Add(typeof(Boolean), OpCodes.Ldind_I1);
            LdindOpCodeTypeMap.Add(typeof(Byte), OpCodes.Ldind_U1);
            LdindOpCodeTypeMap.Add(typeof(SByte), OpCodes.Ldind_I1);
            LdindOpCodeTypeMap.Add(typeof(Int16), OpCodes.Ldind_I2);
            LdindOpCodeTypeMap.Add(typeof(UInt16), OpCodes.Ldind_U2);
            LdindOpCodeTypeMap.Add(typeof(Int32), OpCodes.Ldind_I4);
            LdindOpCodeTypeMap.Add(typeof(UInt32), OpCodes.Ldind_U4);
            LdindOpCodeTypeMap.Add(typeof(Int64), OpCodes.Ldind_I8);
            LdindOpCodeTypeMap.Add(typeof(UInt64), OpCodes.Ldind_I8);
            LdindOpCodeTypeMap.Add(typeof(Char), OpCodes.Ldind_U2);
            LdindOpCodeTypeMap.Add(typeof(Double), OpCodes.Ldind_R8);
            LdindOpCodeTypeMap.Add(typeof(Single), OpCodes.Ldind_R4);

            // 同上，只不过作为 存储。
            StindOpCodeTypeMap = new Dictionary<Type, OpCode>(12);
            StindOpCodeTypeMap.Add(typeof(Boolean), OpCodes.Stind_I1);
            StindOpCodeTypeMap.Add(typeof(Byte), OpCodes.Stind_I1);
            StindOpCodeTypeMap.Add(typeof(SByte), OpCodes.Stind_I1);
            StindOpCodeTypeMap.Add(typeof(Int16), OpCodes.Stind_I2);
            StindOpCodeTypeMap.Add(typeof(UInt16), OpCodes.Stind_I2);
            StindOpCodeTypeMap.Add(typeof(Int32), OpCodes.Stind_I4);
            StindOpCodeTypeMap.Add(typeof(UInt32), OpCodes.Stind_I4);
            StindOpCodeTypeMap.Add(typeof(Int64), OpCodes.Stind_I8);
            StindOpCodeTypeMap.Add(typeof(UInt64), OpCodes.Stind_I8);
            StindOpCodeTypeMap.Add(typeof(Char), OpCodes.Stind_I2);
            StindOpCodeTypeMap.Add(typeof(Double), OpCodes.Stind_R8);
            StindOpCodeTypeMap.Add(typeof(Single), OpCodes.Stind_R4);

        }

        internal MethodImpl GetMethod(int index)
        {
            return _ContractMethods[index];
        }

        internal object CreateInstance()
        {
            lock(LocalMethodsSyncObject)
            {
                if(this.DynamicType == null)
                {
                    TypeInfo tpInfo;
                    if(TypeCahers.TryGetValue(this._ChannelType, out tpInfo))
                    {
                        this.DynamicType = tpInfo.ServiceType;
                        this._ContractMethods = tpInfo.ContractMethods;
                    }
                    else
                    {
                        tpInfo = new TypeInfo();
                        var typeBuilder = NewTypeBuilder(this._ChannelType, this);

                        this._ContractMethods = new List<MethodImpl>(11);

                        foreach(var method in this._ChannelType.GetMethods())
                        {
                            DefineChannelMethod(typeBuilder, method, this._ContractMethods);
                        }

                        foreach(var i in this._ChannelType.GetInterfaces())
                        {
                            foreach(var method in i.GetMethods())
                            {
                                typeBuilder.DefineMethodOverride(DefineChannelMethod(typeBuilder, method, this._ContractMethods), method);
                            }
                            foreach(var property in i.GetProperties())
                            {
                                var propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.None, property.PropertyType, null);
                                if(property.CanRead)
                                {
                                    propertyBuilder.SetGetMethod(DefineChannelMethod(typeBuilder, property.GetGetMethod(), this._ContractMethods));
                                }
                                if(property.CanWrite)
                                {
                                    propertyBuilder.SetSetMethod(DefineChannelMethod(typeBuilder, property.GetSetMethod(), this._ContractMethods));
                                }
                            }
                        }

                        List<PropertyInfo> properties = new List<PropertyInfo>(11);

                        this.DynamicType = typeBuilder.CreateType();
                        tpInfo.ServiceType = this.DynamicType;
                        tpInfo.ContractMethods = this._ContractMethods;
                        TypeCahers.Add(this._ChannelType, tpInfo);
                    }
                }
            }
            try
            {
                return this.DynamicType.GetConstructor(ChannelConstructorArgTypes).Invoke(new object[] { this });
            }
            catch(Exception e)
            {
                this.DynamicType = null;
                if(e.InnerException != null) throw e.InnerException;
                else throw;
            }
        }

        private static void FindTypeMethods(List<MethodInfo> methods, Type type)
        {
            methods.AddRange(type.GetMethods());
            foreach(var property in type.GetProperties())
            {
                if(property.CanRead) methods.Add(property.GetGetMethod());
                if(property.CanWrite) methods.Add(property.GetSetMethod());
            }
        }

        private readonly static AppDomain DynamicDomain
               = Thread.GetDomain();
        private readonly static AssemblyBuilder DynamicAssemblyBuilder
               = DynamicDomain.DefineDynamicAssembly(new AssemblyName("vChannelAssembly"), AssemblyBuilderAccess.Run);
        private readonly static ModuleBuilder DynamicModuleBuilder
                 = DynamicAssemblyBuilder.DefineDynamicModule("vChannelModule", true);

        private static TypeBuilder NewTypeBuilder(Type channelType, RegisterInfo registerInfo)
        {
            string ChannelClassName = channelType.Name + "Proxy";
            if(ChannelClassName[0] == 'I') ChannelClassName = ChannelClassName.Remove(0, 1);
            var typeBuilder = DynamicModuleBuilder.DefineType(ChannelClassName, ChannelTypeAttributes, ChannelBaseType);
            typeBuilder.AddInterfaceImplementation(channelType);
            
            ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, ChannelConstructorArgTypes);
            ConstructorInfo baseCtor = ChannelBaseType.GetConstructor(ChannelConstructorArgTypes);

            /*
             ctor()
             {
                load this                   调用方法的实例
                load endpoint               调用方法的参数
                call base.ctor(String)    调用的方法（父类构造方法，也是一个方法）
                ret
             }
             */

            ILGenerator ctorIL = ctor.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);           //load "this"
            ctorIL.Emit(OpCodes.Ldarg_1);           //load "registerInfo"
            ctorIL.Emit(OpCodes.Call, baseCtor);    //call "base(...)"
            ctorIL.Emit(OpCodes.Ret);
            
            return typeBuilder;
        }

        private static MethodBuilder DefineChannelMethod(TypeBuilder typeBuilder, MethodInfo methodInfo, List<MethodImpl> contractMethods)
        {
            ParameterInfo[] paramInfos = methodInfo.GetParameters();
            int pLength = paramInfos.Length;

            Type[] parameterTypes = new Type[pLength];
            for(int i = 0 ; i < pLength ; i++)
            {
                parameterTypes[i] = paramInfos[i].ParameterType;
            }
            var returnType = methodInfo.ReturnType;
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, ChanneMethodAttributes, returnType, parameterTypes);

            ILGenerator mIL = methodBuilder.GetILGenerator();
            int ident = contractMethods.Count;
            contractMethods.Add(new MethodImpl(ident, methodInfo));

            GenerateILCodeForMethod(ident, mIL, returnType, parameterTypes);
            return methodBuilder;
        }

        private static void LoadOrStoreIndexByType(ILGenerator mIL, Type type, Dictionary<Type, OpCode> dict, OpCode def)
        {
            OpCode code;
            if(dict.TryGetValue(type, out code))
            {
                mIL.Emit(code);
            }
            else
            {
                mIL.Emit(def, type);// 对其他值类型进行存储或加载
            }
        }

        private static void LoadIndexByType(ILGenerator mIL, Type type)
        {
            LoadOrStoreIndexByType(mIL, type, LdindOpCodeTypeMap, OpCodes.Ldobj);
        }

        private static void StoreIndexByType(ILGenerator mIL, Type type)
        {
            LoadOrStoreIndexByType(mIL, type, StindOpCodeTypeMap, OpCodes.Stobj);
        }

        private static void GenerateILCodeForMethod(int ident, ILGenerator mIL, Type returnType, Type[] parameterTypes)
        {
            int pLength = parameterTypes.Length;
            //get the MethodInfo for InvokeMethod

            MethodInfo invokeMethodMI = ChannelInvokeMethod;

            //declare local variables
            LocalBuilder resultLB = mIL.DeclareLocal(Types.ObjectArray); // object[] result

            mIL.Emit(OpCodes.Ldarg_0); //load "this"
            mIL.Emit(OpCodes.Ldc_I4, ident);
            mIL.Emit(OpCodes.Ldc_I4, pLength); //push the number of arguments
            mIL.Emit(OpCodes.Newarr, Types.Object); //create an array of objects, the length is inputArgTypes.Length.

            //store every input argument in the args array
            for(int i = 0 ; i < pLength ; i++)
            {
                Type inputType = parameterTypes[i].IsByRef ? parameterTypes[i].GetElementType() : parameterTypes[i];

                mIL.Emit(OpCodes.Dup);
                mIL.Emit(OpCodes.Ldc_I4, i); //push the index onto the stack
                mIL.Emit(OpCodes.Ldarg, i + 1); //load the i'th argument. This might be an address			
                if(parameterTypes[i].IsByRef)
                {
                    if(inputType.IsValueType)
                    {
                        LoadIndexByType(mIL, inputType);
                        mIL.Emit(OpCodes.Box, inputType);
                    }
                    else
                        mIL.Emit(OpCodes.Ldind_Ref);
                }
                else
                {
                    if(parameterTypes[i].IsValueType)
                        mIL.Emit(OpCodes.Box, parameterTypes[i]);
                }
                mIL.Emit(OpCodes.Stelem_Ref); //store the reference in the args array
            }
            mIL.Emit(OpCodes.Call, invokeMethodMI);
            mIL.Emit(OpCodes.Stloc, resultLB.LocalIndex); //store the result
            //store the results in the arguments
            for(int i = 0 ; i < pLength ; i++)
            {
                if(parameterTypes[i].IsByRef)
                {
                    Type pType = parameterTypes[i].GetElementType();
                    mIL.Emit(OpCodes.Ldarg, i + 1); //load the address of the argument
                    mIL.Emit(OpCodes.Ldloc, resultLB.LocalIndex); //load the result array
                    mIL.Emit(OpCodes.Ldc_I4, i + 1); //load the index into the result array
                    mIL.Emit(OpCodes.Ldelem_Ref); //load the value in the index of the array
                    if(pType.IsValueType)
                    {
                        mIL.Emit(OpCodes.Unbox, pType);
                        LoadIndexByType(mIL, pType);
                        StoreIndexByType(mIL, pType);
                    }
                    else
                    {
                        mIL.Emit(OpCodes.Castclass, pType);
                        mIL.Emit(OpCodes.Stind_Ref); //store the unboxed value at the argument address
                    }
                }
            }
            if(returnType != Types.Void)
            {
                mIL.Emit(OpCodes.Ldloc, resultLB.LocalIndex); //load the result array
                mIL.Emit(OpCodes.Ldc_I4, 0); //load the index of the return value. Alway 0
                mIL.Emit(OpCodes.Ldelem_Ref); //load the value in the index of the array

                if(returnType.IsValueType)
                {
                    mIL.Emit(OpCodes.Unbox, returnType); //unbox it
                    LoadIndexByType(mIL, returnType);
                }
                else mIL.Emit(OpCodes.Castclass, returnType);
            }
            mIL.Emit(OpCodes.Ret);
        }
    }
}
